import React, { useState, useEffect } from 'react';
import { initializeApp } from 'firebase/app';
import { getAuth, signInWithCustomToken, signInAnonymously, onAuthStateChanged } from 'firebase/auth';
import { getFirestore, collection, onSnapshot, doc, setDoc, deleteDoc, writeBatch } from 'firebase/firestore';
// Initialize Firebase with global variables provided by the environment
const firebaseConfig = typeof __firebase_config !== 'undefined' ? JSON.parse(__firebase_config) : {};
const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id';
// Initialize Firebase app and services
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
const auth = getAuth(app);
// --- Icon Components ---
const DeleteIcon = () => (
);
const EditIcon = () => (
);
const FrameDownloadIcon = () => (
);
const UploadIcon = () => (
);
const GenerateIcon = () => (
);
const DownloadIcon = () => (
);
const WhatsappIcon = () => (
);
const SaveIcon = () => (
);
const CopyIcon = () => (
);
const SearchIcon = () => (
);
const DashboardIcon = ({ className }) => (
);
const categories = [
'None', 'All', 'Dentist', 'Children doctor', 'Eye', 'Ent', 'Physiotherapist', 'Skin', 'Orthopadic',
'Cardiologist', 'Neurologist', 'Oncologist', 'Gynecologist', 'Pathologist', 'Lungs',
'Veterinary', 'Radiologist', 'Ayurvedic', 'Other Doctor', 'Car Rental', 'Other',
];
const languages = ['All', 'English', 'Gujarati', 'Hindi'];
function App() {
// --- State Management ---
const [userId, setUserId] = useState(null);
const [frames, setFrames] = useState([]);
const [dailyImage, setDailyImage] = useState(null);
const [generatedPosts, setGeneratedPosts] = useState([]);
const [loading, setLoading] = useState(false);
const [authReady, setAuthReady] = useState(false);
const [error, setError] = useState(null);
const [newFrameName, setNewFrameName] = useState('');
const [newFrameFile, setNewFrameFile] = useState(null);
const [newFrameNumber, setNewFrameNumber] = useState('');
const [newFrameCategory, setNewFrameCategory] = useState('');
const [newFrameLanguage, setNewFrameLanguage] = useState('');
const [newStartDate, setNewStartDate] = useState('');
const [newEndDate, setNewEndDate] = useState('');
const [showModal, setShowModal] = useState(false);
const [modalMessage, setModalMessage] = useState('');
const [modalTitle, setModalTitle] = useState('Error');
const [modalConfig, setModalConfig] = useState(null); // For confirmation modals
const [filterCategory, setFilterCategory] = useState('All');
const [filterLanguage, setFilterLanguage] = useState('All');
const [manageFilterCategory, setManageFilterCategory] = useState('None');
const [manageFilterLanguage, setManageFilterLanguage] = useState('All');
const [editModalOpen, setEditModalOpen] = useState(false);
const [editingFrame, setEditingFrame] = useState(null);
const [editingFrameFile, setEditingFrameFile] = useState(null);
const [currentPage, setCurrentPage] = useState('home');
const [dashboardStats, setDashboardStats] = useState({ total: 0, active: 0, expiring: 0, expired: 0 });
const [searchTerm, setSearchTerm] = useState('');
const [selectedFrames, setSelectedFrames] = useState([]);
const [activeDashboardFilter, setActiveDashboardFilter] = useState('all'); // 'all', 'active', 'expiring', 'expired'
// --- Firestore Path ---
const framesCollectionPath = userId ? `artifacts/${appId}/users/${userId}/frames` : null;
// --- Utility Functions ---
const showCustomModal = (message, title = 'Error', onConfirm = null) => {
if (onConfirm) {
setModalConfig({ message, title, onConfirm });
} else {
setModalMessage(message);
setModalTitle(title);
setShowModal(true);
}
};
// --- Firestore Functions ---
const saveFramesToFirestore = async (newFrame) => {
if (!userId) {
setError("User not authenticated. Cannot save frames.");
return;
}
const docRef = doc(collection(db, framesCollectionPath));
try {
await setDoc(docRef, {
frameName: newFrame.name,
frameUrl: newFrame.url,
whatsappNumber: newFrame.whatsappNumber,
category: newFrame.category,
language: newFrame.language,
startDate: newFrame.startDate,
endDate: newFrame.endDate,
createdAt: new Date().toISOString(),
});
setNewFrameName('');
setNewFrameFile(null);
setNewFrameNumber('');
setNewFrameCategory('');
setNewFrameLanguage('');
setNewStartDate('');
setNewEndDate('');
} catch (e) {
console.error("Error adding document: ", e);
setError("Failed to save frame. Please try again.");
}
};
const handleUpdateFrame = async () => {
if (!editingFrame) return;
const processUpdate = async (frameData) => {
const docRef = doc(db, framesCollectionPath, editingFrame.id);
try {
await setDoc(docRef, frameData, { merge: true });
setEditModalOpen(false);
setEditingFrame(null);
setEditingFrameFile(null);
} catch (e) {
console.error("Error updating document: ", e);
setError("Failed to update frame. Please try again.");
}
};
if (editingFrameFile) {
const reader = new FileReader();
reader.onload = async (event) => {
const updatedFrameData = {
...editingFrame,
frameUrl: event.target.result,
};
await processUpdate(updatedFrameData);
};
reader.readAsDataURL(editingFrameFile);
} else {
await processUpdate(editingFrame);
}
};
const deleteFramesFromFirestore = async (frameIds) => {
if (!userId || frameIds.length === 0) return;
const batch = writeBatch(db);
frameIds.forEach(id => {
const frameDocRef = doc(db, framesCollectionPath, id);
batch.delete(frameDocRef);
});
try {
await batch.commit();
setSelectedFrames([]); // Clear selection after deletion
} catch (e) {
console.error("Error deleting documents: ", e);
setError("Failed to delete selected frames. Please try again.");
}
};
// --- Authentication and Data Fetching ---
useEffect(() => {
const signIn = async () => {
try {
if (typeof __initial_auth_token !== 'undefined') {
await signInWithCustomToken(auth, __initial_auth_token);
} else {
await signInAnonymously(auth);
}
} catch (error) {
console.error("Firebase authentication failed:", error);
setError("Authentication failed. Please refresh the page.");
}
};
signIn();
const unsubscribe = onAuthStateChanged(auth, (user) => {
setUserId(user ? user.uid : null);
setAuthReady(true);
});
return () => unsubscribe();
}, []);
useEffect(() => {
if (!authReady || !userId) return;
const framesCol = collection(db, framesCollectionPath);
const unsubscribe = onSnapshot(framesCol, (snapshot) => {
const fetchedFrames = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data(),
}));
setFrames(fetchedFrames);
}, (e) => {
console.error("Error setting up Firestore listener:", e);
setError("Failed to load frames from the database.");
});
return () => unsubscribe();
}, [authReady, userId, framesCollectionPath]);
// --- Dashboard Stats Calculation ---
useEffect(() => {
const today = new Date();
today.setHours(0, 0, 0, 0); // Normalize today's date
const thirtyDaysFromNow = new Date();
thirtyDaysFromNow.setDate(today.getDate() + 30);
const stats = frames.reduce((acc, frame) => {
acc.total += 1;
const endDate = frame.endDate ? new Date(frame.endDate) : null;
if (endDate) {
endDate.setHours(0, 0, 0, 0); // Normalize end date
if (endDate < today) {
acc.expired += 1;
} else {
acc.active += 1;
if (endDate <= thirtyDaysFromNow) {
acc.expiring += 1;
}
}
} else {
// If no end date, consider it active but not expiring
acc.active += 1;
}
return acc;
}, { total: 0, active: 0, expiring: 0, expired: 0 });
setDashboardStats(stats);
}, [frames]);
// --- Event Handlers ---
const handleFrameFileUpload = (e) => {
const file = e.target.files[0];
if (file) setNewFrameFile(file);
};
const handleAddNewFrame = () => {
if (!newFrameFile || !newFrameName || !newFrameNumber || !newFrameCategory || !newFrameLanguage || !newStartDate || !newEndDate) {
setError("Please fill in all frame details including start and end dates.");
return;
}
const reader = new FileReader();
reader.onload = (event) => {
saveFramesToFirestore({
name: newFrameName,
url: event.target.result,
whatsappNumber: newFrameNumber,
category: newFrameCategory,
language: newFrameLanguage,
startDate: newStartDate,
endDate: newEndDate,
});
};
reader.readAsDataURL(newFrameFile);
};
const handleDailyImageUpload = (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (event) => setDailyImage(event.target.result);
reader.readAsDataURL(file);
}
};
const generatePosts = async () => {
const filteredFrames = frames.filter(frame => {
const isCategoryMatch = filterCategory === 'All' || frame.category === filterCategory;
const isLanguageMatch = filterLanguage === 'All' || frame.language === filterLanguage;
return isCategoryMatch && isLanguageMatch;
});
if (!dailyImage || filteredFrames.length === 0) {
setError("Please upload a daily image and select at least one client's frame using the filters.");
return;
}
setLoading(true);
setGeneratedPosts([]);
setError(null);
const posts = [];
const dailyImg = new Image();
dailyImg.src = dailyImage;
await new Promise(resolve => { dailyImg.onload = resolve; });
for (const frame of filteredFrames) {
const frameImg = new Image();
frameImg.src = frame.frameUrl;
await new Promise(resolve => { frameImg.onload = resolve; });
const canvas = document.createElement('canvas');
canvas.width = frameImg.width;
canvas.height = frameImg.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(dailyImg, 0, 0, canvas.width, canvas.height);
ctx.drawImage(frameImg, 0, 0, canvas.width, canvas.height);
posts.push({
id: frame.id,
name: frame.frameName,
whatsappNumber: frame.whatsappNumber,
url: canvas.toDataURL('image/png'),
});
}
setGeneratedPosts(posts);
setLoading(false);
};
const downloadPost = (post) => {
const link = document.createElement('a');
link.href = post.url;
link.download = `post_${post.whatsappNumber}.png`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
const downloadOriginalFrame = (frame) => {
const link = document.createElement('a');
link.href = frame.frameUrl;
link.download = `original_frame_${frame.frameName.replace(/[^a-z0-9]/gi, '_').toLowerCase()}.png`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
const downloadAllAsPngs = async () => {
if (generatedPosts.length === 0) {
setError("No posts to download.");
return;
}
setLoading(true);
for (const post of generatedPosts) {
downloadPost(post);
await new Promise(resolve => setTimeout(resolve, 300));
}
setLoading(false);
};
const sendToWhatsapp = (post) => {
if (!post.whatsappNumber) {
showCustomModal("No WhatsApp number provided for this client's frame. Please add a number before sending.");
return;
}
const message = encodeURIComponent(`Hello ${post.name}, here is your daily social media post!`);
const whatsappUrl = `https://wa.me/${post.whatsappNumber}?text=${message}`;
window.open(whatsappUrl, '_blank');
};
const handleStartDateChange = (dateString, dateSetter, endDateSetter) => {
dateSetter(dateString);
if (dateString) {
const startDate = new Date(dateString);
const endDate = new Date(startDate);
endDate.setDate(startDate.getDate() + 365);
const year = endDate.getFullYear();
const month = String(endDate.getMonth() + 1).padStart(2, '0');
const day = String(endDate.getDate()).padStart(2, '0');
const formattedEndDate = `${year}-${month}-${day}`;
endDateSetter(formattedEndDate);
} else {
endDateSetter('');
}
};
const copyToClipboard = (textToCopy) => {
const textArea = document.createElement('textarea');
textArea.value = textToCopy;
document.body.appendChild(textArea);
textArea.select();
try {
document.execCommand('copy');
showCustomModal(`${textToCopy} copied to clipboard!`, 'Success');
} catch (err) {
console.error('Failed to copy text: ', err);
setError('Failed to copy number. Please copy it manually.');
}
document.body.removeChild(textArea);
};
const handleDashboardFilterClick = (filter) => {
setActiveDashboardFilter(filter);
setCurrentPage('manage');
// Reset other filters for clarity
setManageFilterCategory('All');
setManageFilterLanguage('All');
setSearchTerm('');
};
// --- Components ---
const Dashboard = ({ stats, onFilterClick }) => (
Generated posts will appear here after you click 'Generate Posts'.Upload Daily Image & Generate Posts
Daily Image Preview:
Generated Posts
{generatedPosts.length > 0 ? (
<>
{frame.frameName}
{(frame.startDate || frame.endDate) && ({frame.startDate} to {frame.endDate}
)}{modalMessage}
{modalConfig.message}
Upload frames and daily images, then instantly generate and send personalized posts.